﻿//----------------------------------------------------------------
// Desc:
// Edit: ShowLong, http://blog.csdn.net/showlong
//
// Date Created: 2010-06-11
//
// Update Date :
//
// Copyright (c) Fuel4D (http://www.fuel4d.com), All rights reserved.
//
//----------------------------------------------------------------
#pragma once


// 分配内存
extern FAPI void* FMemAlloc(size_t size, FNEW_FUNC);
// 释放内存
extern FAPI bool FMemFree(void*& mem);
// 重新分配
extern FAPI void* FMemRealloc(void* p, size_t new_size, FNEW_FUNC);
// 测试分配到的内存大小
extern FAPI size_t FMemSize(void* p);
// 内存整理，释放未用的空间
extern FAPI void FMemTidy(EF_MEMORY_TIDY_TYPE t);
// 设置默认的内存检查输入日志位置
extern FAPI void FMemDefaultLeakFilepath(const ACHAR* p);
// 输出当前内存池使用情况
// .p 为0表示按默认输出到调试控制台，否则请实现 IFMemoryDumper 此接口
// .ud 将被传递到 IFMemoryDumper::DumpObject 接口
struct IFMemoryDumper;
extern FAPI void FMemDump(IFMemoryDumper* p = 0, DWORD_PTR ud = 0);
#if FUEL_OS_WINDOWS
// 设置程序崩溃的时候，将dump发送到指定邮箱
struct FMAILINFOW;
struct FMAILINFOA;
extern FAPI void FDumpAutoSendMailW(FMAILINFOW* mi);
extern FAPI void FDumpAutoSendMailA(FMAILINFOA* mi);
// 虚拟内存查询（和vmmap功能相同）
struct FPROCESS_VIRTUAL_MEMORY
{
    size_t used_size;   // 已用空间
    size_t free_size;   // 未用空间         (vmmap 中 Unusable + Free)
    size_t heap_size;   // 已分配堆         (vmmap 中 Heap + Private Data - Heap(Shareable))
    size_t commit_size; // 已提交大小       (vmmap 中 Commited(Total) - Page Table - Commit(Unusable))
    size_t image_size;  // 镜像占用(.nls)   (vmmap 中 Image)
    size_t thread_size; // 线程占用         (vmmap 中 Thread Stack)
    size_t mapped_size; // 共享文件(.dll)   (vmmap 中 Mapped File)
    size_t share_size;  // 共享内存         (vmmap 中 Shareable)
    size_t max_space[4];// 最大的3个连接空闲空间，第4个是排序用的，不能做为有效数字
};
extern FAPI bool FVMQuery(FPROCESS_VIRTUAL_MEMORY& mem);
#endif


#pragma pack(push, 1)

//----------------------------------------------------------------
// 内存对象节点
//----------------------------------------------------------------
struct FMemoryNode
{
    size_t size;            // 尺寸
    const ACHAR* file;      // 文件
    const ACHAR* func;      // 函数
    const ACHAR* name;      // 类名/类型名/对象名
    size_t line;            // 行号
#ifdef _DEBUG
    DWORD tkc;              // FGetTime()
#endif
};


//----------------------------------------------------------------
// 内存dump数据
//----------------------------------------------------------------
struct FMemoryDumpData
{
    size_t alloc_pool;      // 从池中申请出的内存大小
    size_t alloc_sys;       // 实际从系统中请求的内存大小
    size_t block_count;     // 实际从系统中NEW出来的块数
    size_t used_node;       // 使用中的对象节点数
    size_t cache_node;      // 目前还缓存着的对象节点数
};


//----------------------------------------------------------------
// 该对象分配时的函数调用堆栈
//----------------------------------------------------------------
struct FMemoryDumpCallStack
{
    FMemoryDumpCallStack(){}
    virtual~FMemoryDumpCallStack(){}

    virtual bool QueryCall(size_t max_depth = eCD_MaxCallDepth) = 0;

    ACHAR file[MAX_PATH];
    size_t line;
    ACHAR func[eCD_MaxFuncName];
};


//----------------------------------------------------------------
// 内存信息输出接口
// 注意：
//   1. 在dump期间不可以调用任何与申请内存与释放内存相关操作
//   2. 在dump期间不可以使用非CRT库，如STL、ATL等均不允许
//----------------------------------------------------------------
struct IFMemoryDumper
{
    int idx;
    ACHAR buf[1024];
    ACHAR logfile[MAX_PATH];

    IFMemoryDumper()
        :idx(1)
    {
        ACHAR dt[12] = {0};
        FStrcpyA(logfile, FGetRootPathA());
        FStrcatA(logfile, _FTA("log\\"));
        FStrcatA(logfile, FGetDateCharA(dt));
        FStrcatA(logfile, _FTA("\\leak - "));
        FStrcatA(logfile, FGetExeFilenameA());
        FStrcatA(logfile, _FTA(".log"));
        FMakeDirectoryA(logfile);
    }
    virtual~IFMemoryDumper()
    {}
    virtual void Begin()
    {
        FSaveTextToFileA(_FTA("\n"), logfile);
        OutString(_FTA(">> ----------- Fuel4D -- 内存情况输出 -- 开始 -----------\n"));

        ACHAR dt[12] = {0};
        FSaveTextToFileA(_FTA(">> 打印日期："), logfile);
        FSaveTextToFileA(FGetDateCharA(dt), logfile);
        FSaveTextToFileA(_FTA(", 打印时间："), logfile);
        FSaveTextToFileA(FGetTimeCharA(dt), logfile);
        FSaveTextToFileA(_FTA("\n"), logfile);
    }
    virtual void ThreadBegin(UINT thread_id)
    {
        sprintf(buf, _FTA(">> -- 线程'0x%X'分配内存情况，以及详细分配出的对象列表 --\n"), thread_id);
        OutString(buf);
    }
    // 输出一条内容
    virtual void DumpObject(void* addr, FMemoryNode* nd, FMemoryDumpCallStack* pc, DWORD_PTR ud)
    {
        sprintf(buf, _FTA("    %s(%d): ID=%d,\t地址=0x%.08X,\t大小=%u,\t名字=%s,\t函数=%s\t分配时间=%u\n"), 
            nd->file, nd->line, ++idx, (UINT)addr, nd->size, nd->name, nd->func,
#ifdef _DEBUG
            nd->tkc
#else
            0
#endif
            );
        OutString(buf);
        if (ud && pc)
        {
            OutString(_FTA("    -> 详细调用堆栈如下：\n"));
            while (pc->QueryCall(eCD_MaxCallDepth))
            {
                sprintf(buf, _FTA("        %s(%d): %s\n"), pc->file, pc->line, pc->func);
                OutString(buf);
            }
        }
    }
    virtual void ThreadEnd(UINT thread_id)
    {
        sprintf(buf, _FTA(">> -- 线程'0x%X'结束 --\n"), thread_id);
        OutString(buf);
    }
    virtual void End(FMemoryDumpData* dt)
    {
        sprintf(buf, _FTA(">> 池中已使用对象=%u, 池中还缓存对象=%u, 池中分配出内存=%uKB, 内存池实际请求系统内存=%uKB, 内存池产生系统碎片=%u\n"),
            dt->used_node, dt->cache_node, dt->alloc_pool>>10, dt->alloc_sys>>10, dt->block_count);
        OutString(buf);

#if FUEL_OS_WINDOWS
        FPROCESS_VIRTUAL_MEMORY vm;
        FVMQuery(vm);
        size_t sys_use = vm.image_size + vm.thread_size + vm.mapped_size + vm.share_size;
        sprintf(buf, _FTA(">> 虚拟内存：已用内存%uKB, 已提交内存=%uKB, 进程占用=%uKB, 用户占用=%uKB\n"),
            vm.used_size>>10, vm.commit_size>>10, sys_use>>10, (vm.used_size-sys_use)>>10);
        OutString(buf);
#endif

        OutString(_FTA(">> ----------- Fuel4D -- 内存情况输出 -- 结束 -----------\n"));
    }
    void OutString(const ACHAR* p)
    {
        FOutDebugStringA(p);
        FSaveTextToFileA(p, logfile);
    }
};

#pragma pack(pop)
